package io.github.chutian0610.sqlparser.token

import arrow.core.None
import arrow.core.Option
import arrow.core.Some
import arrow.core.raise.option
import arrow.core.toOption

class Unescape(private val context: TokenizerContext) {
    fun unescape(): Option<String> = option {
        val unescaped = StringBuilder()
        context.next()
        while (context.peek() != null) {
            val char = requireNotNull(context.next()) { "impossible null case" }
            if (CodePoint.equalsChar(char, '\'')) {
                // case ''''
                if (CodePoint.inCharArray(context.peek(), '\'')) {
                    context.next()
                    unescaped.append('\'')
                    continue
                }
                return Option(unescaped.toString())
            }
            if (!CodePoint.inCharArray(char, '\\')) {
                unescaped.append(char.toUnicodeChar())
                continue
            }
            val c = context.next().toOption().bind()
            val escape = when {
                CodePoint.inCharArray(c, 'n') -> CodePoint.fromChar('\n')
                CodePoint.inCharArray(c, 'r') -> CodePoint.fromChar('\r')
                CodePoint.inCharArray(c, 't') -> CodePoint.fromChar('\t')
                CodePoint.inCharArray(c, 'b') -> CodePoint.fromChar('\u0008')
                CodePoint.inCharArray(c, 'f') -> CodePoint.fromChar('\u000C')
                CodePoint.inCharArray(c, 'x') -> unescapeHex().bind()
                CodePoint.inCharArray(c, 'u') -> unescapeUnicode16().bind()
                CodePoint.inCharArray(c, 'U') -> unescapeUnicode32().bind()
                c.isAsciiDigit() -> unescapeOctal(c).bind()
                else -> c
            }
            unescaped.append(escape.toUnicodeChar())
        }
        return None
    }

    private fun checkNull(c: Char): Option<Char> {
        return if (c == '\u0000') {
            None
        } else {
            Some(c)
        }
    }

    // Octal byte value. \o, \oo, \ooo (o = 0–7)
    private fun unescapeOctal(c: CodePoint): Option<CodePoint> {
        val s = StringBuilder()
        s.append(c.toUnicodeChar())

        for (i in 0 until 2) {
            val octalDigit = nextOctalDigest()
            octalDigit.onSome { s.append(it) }
            if (octalDigit.isNone()) {
                break
            }
        }
        return byteToChar(s.toString(), 8)
    }

    private fun unescapeUnicode16(): Option<CodePoint> {
        return unescapeUnicode(4)
    }

    private fun unescapeUnicode32(): Option<CodePoint> {
        return unescapeUnicode(8)
    }

    private fun unescapeUnicode(num: Int): Option<CodePoint> = option {
        val s = StringBuilder()
        for (i in 0 until num) {
            s.append(context.next().toOption().bind().toUnicodeChar())
        }
        val result = fromStrRadix(s.toString(), 16)
        return if (result.isSome()) {
            charFromU32(requireNotNull(result.getOrNull())).toOption()
        } else {
            None
        }
    }

    private fun unescapeHex(): Option<CodePoint> {
        val s = StringBuilder()
        for (i in 0 until 2) {
            val hexDigit = nextHexDigit()
            hexDigit.onSome { s.append(it.toUnicodeChar()) }
            if (hexDigit.isNone()) {
                break
            }
        }
        if (s.isEmpty()) {
            return Option(CodePoint.fromChar('x'))
        }
        return byteToChar(s.toString(), 16)
    }

    private fun byteToChar(s: String, radix: Int): Option<CodePoint> {
        return when (val result = fromStrRadix(s, radix)) {
            is Some -> {
                val n = requireNotNull(result.getOrNull()) and 255u
                if (n <= 127u) {
                    charFromU32(n).toOption()
                } else {
                    None
                }
            }

            is None -> None
        }
    }

    private fun nextHexDigit(): Option<CodePoint> {
        context.peek()
            ?.let {
                if (it.isAsciiDigit()) {
                    return Option(requireNotNull(context.next()))
                }
            }
        return None
    }

    private fun nextOctalDigest(): Option<CodePoint> {
        context.peek()
            ?.let {
                if (it.isDigit(8)) {
                    return Option(requireNotNull(context.next()))
                }
            }
        return None
    }

    companion object {
        fun charFromU32(codePoint: UInt): CodePoint? {
            return when (codePoint) {
                // 代理对码点范围 (0xD800..0xDFFF) - 无效
                in 0xD800u..0xDFFFu -> null
                // 超出 Unicode 范围 (> 0x10FFFF) - 无效
                in 0x110000u..UInt.MAX_VALUE -> null
                // 有效的 Unicode 标量值，转换为 Int 返回
                else -> CodePoint.fromInt(codePoint.toInt())
            }
        }

        fun fromStrRadix(s: String, radix: Int): Option<UInt> {
            return Option.fromNullable(s.toUIntOrNull(radix))
        }
    }
}